﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;

namespace Foundry.Graphics
{
    public abstract class Texture : IHandle, IDisposable
    {
        public int Handle { get; private set; }
        public event EventHandler Disposing;
        public bool Disposed { get; private set; }

        protected TextureTarget Target { get; set; }

        public void MakeCurrent()
        {
            if (Disposed)
                throw new ObjectDisposedException(GetType().FullName);

            GL.BindTexture(Target, Handle);
        }
        public void MakeNullCurrent()
        {
            GL.BindTexture(Target, 0);
        }

        internal Texture()
        {
            GraphicsContext.Assert();
            Handle = GL.GenTexture();
        }
		
		~Texture()
		{
			var handle = Handle;
			Foundry.Engine.App.CurrentApplication.DispatchResourceCallStack(() => GL.DeleteTexture(handle));
		}
		
        public void Dispose()
        {
			if(OpenTK.Graphics.GraphicsContext.CurrentContext != null && !Disposed)
			{
	            if (Disposing != null)
	                Disposing(this, EventArgs.Empty);
	
	            GL.DeleteTexture(Handle);
	            Handle = 0;
	            Disposed = true;
			}
        }

        public TextureMagFilter MagnificationFilter
        {
            get
            {
                int result = 0;
                GL.GetTexParameterI(Target, GetTextureParameter.TextureMagFilter, out result);
                return (TextureMagFilter)result;
            }
            set
            {
                GL.TexParameterI(Target, TextureParameterName.TextureMagFilter, new int[] { (int)value });
            }
        }

        public TextureMinFilter MinificationFilter
        {
            get
            {
                int result = 0;
                GL.GetTexParameterI(Target, GetTextureParameter.TextureMinFilter, out result);
                return (TextureMinFilter)result;
            }
            set
            {
                GL.TexParameterI(Target, TextureParameterName.TextureMinFilter, new int[] { (int)value });
            }
        }
    }
    public sealed class Texture1D : Texture
    {
        public int Width { get; private set; }

        public Texture1D()
            : base()
        {
            Target = TextureTarget.Texture1D;
        }

        public static Texture1D Create(IEnumerable<Color4> generator)
        {
            var data = generator.ToArray();
            Texture1D result = new Texture1D();
            result.MakeCurrent();
            GL.TexImage1D(TextureTarget.Texture1D, 0, PixelInternalFormat.Rgba, data.Length, 0, PixelFormat.Rgba, PixelType.Float, data);
            result.MakeNullCurrent();
            return result;
        }

        public TextureWrapMode WrapX
        {
            get
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                int result = 0;
                GL.GetTexParameterI(Target, GetTextureParameter.TextureWrapS, out result);
                return (TextureWrapMode)result;
            }
            set
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                GL.TexParameterI(Target, TextureParameterName.TextureWrapS, new int[] { (int)value });
            }
        }

    }
    public sealed class Texture2D : Texture
    {
        public int Width { get; private set; }
        public int Height { get; private set; }

        public Texture2D()
            : base()
        {
            Target = TextureTarget.Texture2D;
        }

        public static Texture2D FromBitmap(System.Drawing.Bitmap source)
        {
            Texture2D result = new Texture2D();
            var data = source.LockBits(new System.Drawing.Rectangle(0, 0, source.Width, source.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, source.PixelFormat);
            result.MakeCurrent();
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, source.Width, result.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, data.Scan0);
            result.MakeNullCurrent();
            source.UnlockBits(data);
            return result;
        }

        public TextureWrapMode WrapX
        {
            get
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                int result = 0;
                GL.GetTexParameterI(Target, GetTextureParameter.TextureWrapS, out result);
                return (TextureWrapMode)result;
            }
            set
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                GL.TexParameterI(Target, TextureParameterName.TextureWrapS, new int[] { (int)value });
            }
        }

        public TextureWrapMode WrapY
        {
            get
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                int result = 0;
                GL.GetTexParameterI(Target, GetTextureParameter.TextureWrapT, out result);
                return (TextureWrapMode)result;
            }
            set
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                GL.TexParameterI(Target, TextureParameterName.TextureWrapT, new int[] { (int)value });
            }
        }


    }
    public sealed class Texture3D : Texture
    {
        public int Width { get; private set; }
        public int Height { get; private set; }
        public int Depth { get; private set; }

        public Texture3D()
            : base()
        {
            Target = TextureTarget.Texture3D;
        }
        public TextureWrapMode WrapX
        {
            get
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                int result = 0;
                GL.GetTexParameterI(Target, GetTextureParameter.TextureWrapS, out result);
                return (TextureWrapMode)result;
            }
            set
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                GL.TexParameterI(Target, TextureParameterName.TextureWrapS, new int[] { (int)value });
            }
        }

        public TextureWrapMode WrapY
        {
            get
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                int result = 0;
                GL.GetTexParameterI(Target, GetTextureParameter.TextureWrapT, out result);
                return (TextureWrapMode)result;
            }
            set
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                GL.TexParameterI(Target, TextureParameterName.TextureWrapT, new int[] { (int)value });
            }
        }

        public TextureWrapMode WrapZ
        {
            get
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                int result = 0;
                GL.GetTexParameterI(Target, GetTextureParameter.TextureWrapR, out result);
                return (TextureWrapMode)result;
            }
            set
            {
                if (Disposed)
                    throw new ObjectDisposedException(GetType().FullName);

                GL.TexParameterI(Target, TextureParameterName.TextureWrapR, new int[] { (int)value });
            }
        }
    }

    public class CubeTexture : Texture
    {
        public CubeTexture()
        {
            Target = TextureTarget.TextureCubeMap;
        }
    }

}
